using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Data;
using System.Data.Metadata.Edm;
using System.Data.Objects;
using System.Data.Objects.DataClasses;
using Microsoft.Practices.ObjectBuilder2;

namespace Common.Unity.Extensions.Entity
{
	public static class MetadataHelper
	{
		static readonly Dictionary<Type, EntityType> entityTypes = new Dictionary<Type, EntityType>();

		public static EntityType GetEntityMetaData<T>( ObjectContext context ) where T : EntityObject
		{
			return GetEntityMetaData( context.MetadataWorkspace, typeof(T) );
		}

		public static EntityType GetEntityMetaData( MetadataWorkspace workspace, Type entityType )
		{
			if ( !entityTypes.ContainsKey( entityType ) )
			{
				entityTypes.Add( entityType, (EntityType)workspace.GetType( entityType.Name, entityType.Namespace, true, DataSpace.CSpace ) );	
			}
			return entityTypes[entityType];
		}

		public static EntityKey CreateKey<T, U>( ObjectContext context, U id ) where T : EntityObject where U : struct
		{
			EntityType type = GetEntityMetaData<T>( context );
			List<EntityKeyMember> list = new List<EntityKeyMember>();
			list.Add( new EntityKeyMember( new List<EdmMember>(type.KeyMembers)[0].Name, id ) );
			return CreateKey( context, typeof(T), list );
		}

		public static EntityKey CreateKey( ObjectContext context, Type entityType,
		                                   IEnumerable<EntityKeyMember> pairs )
		{
			Guard.ArgumentNotNull( context, "context" );
			Guard.ArgumentNotNull( entityType, "entityType" );
			Guard.ArgumentNotNull( pairs, "pairs" );

			string name = string.Concat( context.DefaultContainerName, ".", ResolveEntitySet( context, entityType ) );
			EntityKey result = new EntityKey( name, pairs );
			return result;
		}

		public static EntitySet ResolveEntitySet( ObjectContext context, Type entityType )
		{
			EntityContainer container = context.MetadataWorkspace.GetEntityContainer( context.GetType().Name, DataSpace.CSpace );
			foreach ( EntitySet entitySet in container.BaseEntitySets.OfType<EntitySet>() )
			{
				Type result = Type.GetType( string.Format( "{0}, {1}", entitySet.ElementType.FullName, context.GetType().Assembly.FullName ) );
				if ( result.IsAssignableFrom( entityType ) || result.IsSubclassOf( entityType ) )
				{
					return entitySet;
				}
			}
			return null;
		}

		public static EntityKey CreateKey<T>( ObjectContext context, IOrderedDictionary values ) where T : EntityObject
		{
			Guard.ArgumentNotNull( context, "context" );
			Guard.ArgumentNotNull( values, "values" );

			List<EntityKeyMember> items = new List<EntityKeyMember>();
			EntityType metadata = GetEntityMetaData<T>( context );
			foreach ( EdmProperty property in ConvertProperties<EdmProperty, EdmMember>( metadata.KeyMembers ) )
			{
				if ( values.Contains( property.Name ) )
				{
					items.Add( new EntityKeyMember( property.Name, values[property.Name] ) );
				}
				else
				{
					throw new ArgumentNullException( "key", "A property that's part of a key was not found." );
				}
			}
			return CreateKey( context, typeof(T), items );
		}

		public static EntityKey ExtractKey( ObjectContext context, object target )
		{
			Guard.ArgumentNotNull( context, "context" );
			Guard.ArgumentNotNull( target, "target" );
			return ExtractKey( context, target.GetType(), target );
		}

		public static EntityKey ExtractKey( ObjectContext context, Type type, object target )
		{
			Guard.ArgumentNotNull( context, "context" );
			Guard.ArgumentNotNull( type, "type" );
			EntityType metadata = GetEntityMetaData( context.MetadataWorkspace, type );
			try
			{
				List<EntityKeyMember> pairs = ExtractKeyValues( metadata, target );
				EntityKey result = CreateKey( context, type, pairs );
				return result;
			}
			catch
			{
				return null;
			}
		}

		public static List<EntityKeyMember> ExtractKeyValues( EntityType type, object target )
		{
			IDictionary dictionary = target as IDictionary;
			Type targetType = target.GetType();
			List<EntityKeyMember> result = new List<EntityKeyMember>();
			foreach ( EdmProperty property in ConvertProperties<EdmProperty,EdmMember>( type.KeyMembers ) )
			{
				object value = dictionary != null && dictionary.Contains( property.Name )
				               	? dictionary[ property.Name ]
				               	: targetType.GetProperty( property.Name ).GetValue( target, null );
				result.Add( new EntityKeyMember( property.Name, value ) );
			}
			return result;
		}

		internal static IEnumerable<T> ConvertProperties<T,U>( IEnumerable<U> members ) where T : MetadataItem where U : MetadataItem
		{
			foreach ( U member in members )
			{
				if ( member is T )
				{
					yield return member as T;
				}
			}
		}

		internal static IEnumerable<T> ConvertProperties<T>( IEnumerable<MetadataItem> members ) where T : MetadataItem
		{
			return ConvertProperties<T, MetadataItem>( members );
		}
	}
}